package com.linran.utils.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 基于FFmpeg内核来编解码音视频信息；
 * 使用前需手动在运行环境中安装FFmpeg运行程序，然后正确设置FFmpeg运行路径后MediaUtil.java才能正常调用到FFmpeg程序去处理音视频；
 */
@Slf4j
public class MediaUtil {

    /**
     * 可以处理的视频格式
     */
    public final static String[] VIDEO_TYPE = {"MP4", "WMV"};

    /**
     * 可以处理的音频格式
     */
    public final static String[] AUDIO_TYPE = {"AAC"};

    /**
     * FFmpeg程序执行路径
     * 当前系统安装好ffmpeg程序并配置好相应的环境变量后，值为ffmpeg可执行程序文件在实际系统中的绝对路径
     */
    private static String FFMPEG_PATH = "ffmpeg"; // /usr/bin/ffmpeg


    /**
     * 视频时长正则匹配式
     * 用于解析视频及音频的时长等信息时使用；
     * <p>
     * (.*?)表示：匹配任何除\r\n之外的任何0或多个字符，非贪婪模式
     */
    private static String durationRegex = "Duration: (\\d*?):(\\d*?):(\\d*?)\\.(\\d*?), start: (.*?), bitrate: (\\d*) kb\\/s.*";
    private static Pattern durationPattern;
    /**
     * 视频流信息正则匹配式
     * 用于解析视频详细信息时使用；
     */
    private static String videoStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Video: (\\S*\\S$?)[^\\,]*, (.*?), (\\d*)x(\\d*)[^\\,]*, (\\d*) kb\\/s, (\\d*[\\.]??\\d*) fps";
    private static Pattern videoStreamPattern;
    /**
     * 音频流信息正则匹配式
     * 用于解析音频详细信息时使用；
     */
    private static String musicStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Audio: (\\S*\\S$?)(.*), (.*?) Hz, (.*?), (.*?), (\\d*) kb\\/s";
    ;
    private static Pattern musicStreamPattern;

    /**
     * 静态初始化时先加载好用于音视频解析的正则匹配式
     */
    static {
        durationPattern = Pattern.compile(durationRegex);
        videoStreamPattern = Pattern.compile(videoStreamRegex);
        musicStreamPattern = Pattern.compile(musicStreamRegex);
    }

    /**
     * 执行FFmpeg命令
     *
     * @param commonds 要执行的FFmpeg命令
     * @return FFmpeg程序在执行命令过程中产生的各信息，执行出错时返回null
     */
    public static String executeCommand(List<String> commonds) {
        if (CollectionUtils.isEmpty(commonds)) {
            log.error("--- 指令执行失败，因为要执行的FFmpeg指令为空！ ---");
            return null;
        }
        LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
        ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径
        log.info("--- 待执行的FFmpeg指令为：---" + ffmpegCmds);

        Runtime runtime = Runtime.getRuntime();
        Process ffmpeg = null;
        try {
            // 执行ffmpeg指令
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(ffmpegCmds);
            ffmpeg = builder.start();
            log.info("--- 开始执行FFmpeg指令：--- 执行线程名：" + builder.toString());

            // 取出输出流和错误流的信息
            // 注意：必须要取出ffmpeg在执行命令过程中产生的输出信息，如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时，线程就回阻塞住
            PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
            PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
            errorStream.start();
            inputStream.start();
            // 等待ffmpeg命令执行完
            ffmpeg.waitFor();

            // 获取执行结果字符串
            String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();

            // 输出执行的命令信息
            String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
            String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常";
            log.info("--- 已执行的FFmepg命令： ---" + cmdStr + " 已执行完毕,执行结果： " + resultStr);
            return result;

        } catch (Exception e) {
            log.error("--- FFmpeg命令执行出错！ --- 出错信息： " + e.getMessage());
            return null;

        } finally {
            if (null != ffmpeg) {
                ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
                // JVM退出时，先通过钩子关闭FFmepg进程
                runtime.addShutdownHook(ffmpegKiller);
            }
        }
    }

    /**
     * 解析视频的基本信息（从文件中）
     * <p>
     * 解析出的视频信息一般为以下格式：
     * Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '6.mp4':
     * Duration: 00:00:30.04, start: 0.000000, bitrate: 19031 kb/s
     * Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 18684 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
     * Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
     * <p>
     * 注解：
     * Duration: 00:00:30.04【视频时长】, start: 0.000000【视频开始时间】, bitrate: 19031 kb/s【视频比特率/码率】
     * Stream #0:0(eng): Video: h264【视频编码格式】 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080【视频分辨率，宽x高】, 18684【视频比特率】 kb/s, 25【视频帧率】 fps, 25 tbr, 25k tbn, 50 tbc (default)
     * Stream #0:1(eng): Audio: aac【音频格式】 (LC) (mp4a / 0x6134706D), 48000【音频采样率】 Hz, stereo, fltp, 317【音频码率】 kb/s (default)
     *
     * @param videoFile 源视频路径
     * @return 视频的基本信息，解码失败时返回null
     */
    public static void getVideoMetaInfo(File videoFile) {
        if (null == videoFile || !videoFile.exists()) {
            log.error("--- 解析视频信息失败，因为要解析的源视频文件不存在 ---");
            return;
        }

        String parseResult = getMetaInfoFromFFmpeg(videoFile);

        Matcher durationMacher = durationPattern.matcher(parseResult);
        Matcher videoStreamMacher = videoStreamPattern.matcher(parseResult);
        Matcher videoMusicStreamMacher = musicStreamPattern.matcher(parseResult);

        Long duration = 0L; // 视频时长
        Integer videoBitrate = 0; // 视频码率
        String videoFormat = getFormat(videoFile); // 视频格式
        Long videoSize = videoFile.length(); // 视频大小

        String videoEncoder = ""; // 视频编码器
        Integer videoHeight = 0; // 视频高度
        Integer videoWidth = 0; // 视频宽度
        Float videoFramerate = 0F; // 视频帧率

        String musicFormat = ""; // 音频格式
        Long samplerate = 0L; // 音频采样率
        Integer musicBitrate = 0; // 音频码率

        try {
            // 匹配视频播放时长等信息
            if (durationMacher.find()) {
                long hours = (long) Integer.parseInt(durationMacher.group(1));
                long minutes = (long) Integer.parseInt(durationMacher.group(2));
                long seconds = (long) Integer.parseInt(durationMacher.group(3));
                long dec = (long) Integer.parseInt(durationMacher.group(4));
                duration = dec * 100L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
                //String startTime = durationMacher.group(5) + "ms";
                videoBitrate = Integer.parseInt(durationMacher.group(6));
            }
            // 匹配视频分辨率等信息
            if (videoStreamMacher.find()) {
                videoEncoder = videoStreamMacher.group(1);
                String s2 = videoStreamMacher.group(2);
                videoWidth = Integer.parseInt(videoStreamMacher.group(3));
                videoHeight = Integer.parseInt(videoStreamMacher.group(4));
                String s5 = videoStreamMacher.group(5);
                videoFramerate = Float.parseFloat(videoStreamMacher.group(6));
            }
            // 匹配视频中的音频信息
            if (videoMusicStreamMacher.find()) {
                musicFormat = videoMusicStreamMacher.group(1); // 提取音频格式
                samplerate = Long.parseLong(videoMusicStreamMacher.group(3)); // 提取采样率
                musicBitrate = Integer.parseInt(videoMusicStreamMacher.group(6)); // 提取比特率
            }
        } catch (Exception e) {
            log.error("--- 解析视频参数信息出错！ --- 错误信息： " + e.getMessage());
            return;
        }

        // 封装视频中的音频信息
//        MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
//        musicMetaInfo.setFormat(musicFormat);
//        musicMetaInfo.setDuration(duration);
//        musicMetaInfo.setBitRate(musicBitrate);
//        musicMetaInfo.setSampleRate(samplerate);
//        // 封装视频信息
//        VideoMetaInfo videoMetaInfo = new VideoMetaInfo();
//        videoMetaInfo.setFormat(videoFormat);
//        videoMetaInfo.setSize(videoSize);
//        videoMetaInfo.setBitRate(videoBitrate);
//        videoMetaInfo.setDuration(duration);
//        videoMetaInfo.setEncoder(videoEncoder);
//        videoMetaInfo.setFrameRate(videoFramerate);
//        videoMetaInfo.setHeight(videoHeight);
//        videoMetaInfo.setWidth(videoWidth);
//        videoMetaInfo.setMusicMetaInfo(musicMetaInfo);
    }

    /**
     * 获取视频的基本信息（从流中）
     *
     * @param inputStream 源视频流路径
     * @return 视频的基本信息，解码失败时返回null
     */
    public static void getVideoMetaInfo(InputStream inputStream) {
//        VideoMetaInfo videoInfo = new VideoMetaInfo();
        try {
            File file = File.createTempFile("tmp", null);
            if (!file.exists()) {
                return;
            }
//            FileUtils.copyInputStreamToFile(inputStream, file);
//            videoInfo = getVideoMetaInfo(file);
            file.deleteOnExit();
            return;
        } catch (Exception e) {
            log.error("--- 从流中获取视频基本信息出错 --- 错误信息： " + e.getMessage());
            return;
        }
    }


    /**
     * 检查文件类型是否是给定的类型
     *
     * @param inputFile   源文件
     * @param givenFormat 指定的文件类型；例如：{"MP4", "AVI"}
     * @return
     */
    public static boolean isGivenFormat(File inputFile, String[] givenFormat) {
        if (null == inputFile || !inputFile.exists()) {
            log.error("--- 无法检查文件类型是否满足要求，因为要检查的文件不存在 --- 源文件： " + inputFile);
            return false;
        }
        if (null == givenFormat || givenFormat.length <= 0) {
            log.error("--- 无法检查文件类型是否满足要求，因为没有指定的文件类型 ---");
            return false;
        }
        String fomat = getFormat(inputFile);
        return isLegalFormat(fomat, givenFormat);
    }

    /**
     * 使用FFmpeg的"-i"命令来解析视频信息
     *
     * @param inputFile 源媒体文件
     * @return 解析后的结果字符串，解析失败时为空
     */
    public static String getMetaInfoFromFFmpeg(File inputFile) {
        if (inputFile == null || !inputFile.exists()) {
            throw new RuntimeException("源媒体文件不存在，源媒体文件路径： ");
        }
        List<String> commond = new ArrayList<String>();
        commond.add("-i");
        commond.add(inputFile.getAbsolutePath());
        String executeResult = MediaUtil.executeCommand(commond);
        return executeResult;
    }

    /**
     * 检测视频格式是否合法
     *
     * @param format
     * @param formats
     * @return
     */
    private static boolean isLegalFormat(String format, String formats[]) {
        for (String item : formats) {
            if (item.equals(StringUtils.upperCase(format))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取指定文件的后缀名
     *
     * @param file
     * @return
     */
    private static String getFormat(File file) {
        String fileName = file.getName();
        String format = fileName.substring(fileName.indexOf(".") + 1);
        return format;
    }


    /**
     * 在程序退出前结束已有的FFmpeg进程
     */
    private static class ProcessKiller extends Thread {
        private Process process;

        public ProcessKiller(Process process) {
            this.process = process;
        }

        @Override
        public void run() {
            this.process.destroy();
            log.info("--- 已销毁FFmpeg进程 --- 进程名： " + process.toString());
        }
    }


    /**
     * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
     */
    static class PrintStream extends Thread {
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer = new StringBuffer();

        public PrintStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public void run() {
            try {
                if (null == inputStream) {
                    log.error("--- 读取输出流出错！因为当前输出流为空！---");
                }
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    //log.info(line);
                    stringBuffer.append(line);
                }
            } catch (Exception e) {
                log.error("--- 读取输入流出错了！--- 错误信息：" + e.getMessage());
            } finally {
                try {
                    if (null != bufferedReader) {
                        bufferedReader.close();
                    }
                    if (null != inputStream) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    log.error("--- 调用PrintStream读取输出流后，关闭流时出错！---");
                }
            }
        }
    }

}